package com.example.redisstudy.redisCache.service.impl;

import cn.hutool.core.util.BooleanUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.example.redisstudy.redisCache.domain.Result;
import com.example.redisstudy.redisCache.domain.Shop;
import com.example.redisstudy.redisCache.mapper.ShopMapper;
import com.example.redisstudy.redisCache.service.ShopService;
import com.example.redisstudy.redisCache.utils.RedisData;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.time.LocalDateTime;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

import static com.example.redisstudy.redisCache.utils.RedisConstants.CACHE_SHOP_KEY;
import static com.example.redisstudy.redisCache.utils.RedisConstants.LOCK_SHOP_KEY;

/**
 * @author li290
 * @description 针对表【tb_shop】的数据库操作Service实现
 * @createDate 2022-06-23 19:24:48
 */
@Service
public class ShopServiceImpl extends ServiceImpl<ShopMapper, Shop>
        implements ShopService {

    private static final ExecutorService CACHE_REBUILD_EXECUTOR = Executors.newFixedThreadPool(10);
    @Resource
    StringRedisTemplate stringRedisTemplate;

    /**
     * @param id
     * @return
     */
    @Override
    public Result queryById(Long id) {

        return queryWithLogicalExpire(id);
    }

    /**
     * 缓存击穿：逻辑过期方案
     *
     * @param id
     * @return
     */
    public Result queryWithLogicalExpire(Long id) {
        //1.读缓存
        String shopJson = stringRedisTemplate.opsForValue().get(CACHE_SHOP_KEY + id);
        //1.1没命中，isBlank,空白的定义是null,"",不可见字符（如空格）
        if (StrUtil.isBlank(shopJson)) {
            return Result.fail("店铺信息不存在！");
        }

        //2.命中
        RedisData redisData = JSONUtil.toBean(shopJson, RedisData.class);
        if (redisData.getExpireTime().isAfter(LocalDateTime.now())) {
            //2.1 缓存没过期
            return Result.ok(redisData.getData());
        } else {
            //2.2 缓存过期
            if (!tryLock(LOCK_SHOP_KEY + id)) {
                //2.2.1 获取锁失败，返回旧数据
                return Result.ok(redisData.getData());
            } else {
                //2.2.2 获取锁成功
                CACHE_REBUILD_EXECUTOR.submit(
                        () -> {
                            try {
                                saveShop2Redis(id, 20L);
                            } catch (Exception e) {
                                throw new RuntimeException(e);
                            } finally {
                                unLock(LOCK_SHOP_KEY + id);
                            }
                        }

                );


            }

        }

        return Result.ok(redisData.getData());
    }

    public void saveShop2Redis(Long id, Long expireSeconds) {
        Shop shop = getById(id);
        RedisData<Shop> redisData = new RedisData<>(shop, LocalDateTime.now().plusSeconds(expireSeconds));
        stringRedisTemplate.opsForValue().set(CACHE_SHOP_KEY + id, JSONUtil.toJsonStr(redisData));
    }

    /**
     * 缓存击穿：互斥锁方案
     *
     * @param id
     * @return
     */
    public Result queryWithMutex(Long id) {

        //1.读缓存
        String shopJson = stringRedisTemplate.opsForValue().get(CACHE_SHOP_KEY + id);
        //1.1命中，isNotBlank,空白的定义是null,"",不可见字符（如空格）
        if (StrUtil.isNotBlank(shopJson)) {
            Shop shop = JSONUtil.toBean(shopJson, Shop.class);
            return Result.ok(shop);
        }

        //1.2命中为空串(空对象缓存为空串)
        if (shopJson != null) {
            // 返回一个错误信息
            return Result.fail("店铺信息不存在！");
        }

        //2.未命中,缓存重建
        //2.1 获取互斥锁
        boolean lock = tryLock(LOCK_SHOP_KEY + id);
        try {
            if (!lock) {
                //2.2 获取不到锁，休眠，并返回重新调用此方法
                Thread.sleep(50);
                queryWithMutex(id);
            } else {
                //2.3 获取到锁，查数据库
                Shop shop = getById(id);
                if (shop != null) {
                    stringRedisTemplate.opsForValue().set(CACHE_SHOP_KEY + id, JSONUtil.toJsonStr(shop));
                    return Result.ok(shop);
                }

                stringRedisTemplate.opsForValue().set(CACHE_SHOP_KEY + id, "");

                //释放锁
            }
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        } finally {
            unLock(LOCK_SHOP_KEY + id);
        }


        return Result.fail("店铺信息不存在！");


    }

    /**
     * 缓存穿透
     *
     * @param id
     * @return
     */
    public Result queryWithPassThrough(Long id) {
        //1.读缓存
        String shopJson = stringRedisTemplate.opsForValue().get(CACHE_SHOP_KEY + id);
        //1.1命中，isNotBlank,空白的定义是null,"",不可见字符（如空格）
        if (StrUtil.isNotBlank(shopJson)) {
            Shop shop = JSONUtil.toBean(shopJson, Shop.class);
            return Result.ok(shop);
        }

        //1.2命中为空串(空对象缓存为空串)
        if (shopJson != null) {
            // 返回一个错误信息
            return Result.fail("店铺信息不存在！");
        }

        //2.未命中
        Shop shop = getById(id);
        if (shop != null) {
            stringRedisTemplate.opsForValue().set(CACHE_SHOP_KEY + id, JSONUtil.toJsonStr(shop));
            return Result.ok(shop);
        }

        stringRedisTemplate.opsForValue().set(CACHE_SHOP_KEY + id, "");
        return Result.fail("店铺信息不存在！");
    }

    @Override
    @Transactional
    public Result update(Shop shop) {
        Long id = shop.getId();
        if (id == null) {
            return Result.fail("店铺id不能为空");
        }
        // 1.更新数据库
        updateById(shop);
        // 2.删除缓存
        stringRedisTemplate.delete(CACHE_SHOP_KEY + id);
        return Result.ok();
    }

    @Override
    public Result queryShopByType(Integer typeId, Integer current, Double x, Double y) {
        return null;
    }


    private boolean tryLock(Object key) {
        Boolean result = stringRedisTemplate.opsForValue().setIfAbsent((String) key, "1", 5L, TimeUnit.SECONDS);
        //防止拆箱导致空指针
        return BooleanUtil.isTrue(result);
    }

    private void unLock(Object key) {
        stringRedisTemplate.delete((String) key);
    }
}




